﻿using FunArts.EasyDevOps.Shared.Models.SshClient.KeepAlivedClient;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunArts.EasyDevOps.Rcl.Repertories.Impl
{
    public class KeepalivedConfigRepository: IKeepalivedConfigRepository
    {
        /// <summary>
        /// 解析Keepalived配置文件为对象模型
        /// </summary>
        public KeepalivedConfig Parse(string configText)
        {
            var config = new KeepalivedConfig();

            try
            {
                var lines = configText.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
                var currentBlock = "";
                var blockDepth = 0;

                for (int i = 0; i < lines.Length; i++)
                {
                    var line = lines[i].Trim();

                    if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
                        continue;

                    // 处理块开始
                    if (line.EndsWith("{"))
                    {
                        blockDepth++;
                        var blockName = line.Substring(0, line.IndexOf('{')).Trim();

                        if (blockName == "global_defs")
                        {
                            currentBlock = "global_defs";
                            ParseGlobalDefs(config.GlobalDefs, lines, ref i);
                        }
                        else if (blockName.StartsWith("vrrp_instance"))
                        {
                            currentBlock = "vrrp_instance";
                            config.VrrpInstances.Add(ParseVrrpInstance(lines, ref i));
                        }
                        else if (blockName.StartsWith("virtual_server"))
                        {
                            currentBlock = "virtual_server";
                            config.VirtualServers.Add(ParseVirtualServer(lines, ref i));
                        }
                        else if (blockName.StartsWith("vrrp_script"))
                        {
                            currentBlock = "vrrp_script";
                            config.Scripts.Add(ParseVrrpScript(lines, ref i));
                        }
                    }
                    // 处理块结束
                    else if (line == "}")
                    {
                        blockDepth--;
                        if (blockDepth == 0)
                            currentBlock = "";
                    }
                }
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("解析Keepalived配置失败", ex);
            }

            return config;
        }

        /// <summary>
        /// 将对象模型生成Keepalived配置文件
        /// </summary>
        public string Generate(KeepalivedConfig config)
        {
            var sb = new StringBuilder();

            // 生成global_defs
            sb.AppendLine("global_defs {");
            sb.AppendLine($"    router_id {config.GlobalDefs.RouterId};");

            if (config.GlobalDefs.NotificationEmails.Any())
            {
                sb.AppendLine("    notification_email {");
                foreach (var email in config.GlobalDefs.NotificationEmails)
                    sb.AppendLine($"        {email}");
                sb.AppendLine("    }");
                sb.AppendLine($"    notification_email_from {config.GlobalDefs.NotificationEmailFrom};");
                sb.AppendLine($"    smtp_server {config.GlobalDefs.SmtpServer};");
                sb.AppendLine($"    smtp_connect_timeout {config.GlobalDefs.SmtpConnectTimeout};");
            }

            if (config.GlobalDefs.VrrpStrict)
                sb.AppendLine("    vrrp_strict;");

            if (config.GlobalDefs.VrrpSkipCheckAdvAddr)
                sb.AppendLine("    vrrp_skip_check_adv_addr;");

            if (config.GlobalDefs.VrrpGarpInterval > 0)
                sb.AppendLine($"    vrrp_garp_interval {config.GlobalDefs.VrrpGarpInterval};");

            if (config.GlobalDefs.VrrpGnaInterval > 0)
                sb.AppendLine($"    vrrp_gna_interval {config.GlobalDefs.VrrpGnaInterval};");

            sb.AppendLine("}");

            // 生成vrrp_scripts
            foreach (var script in config.Scripts)
            {
                sb.AppendLine();
                sb.AppendLine($"vrrp_script {script.Name} {{");
                sb.AppendLine($"    script \"{script.Script}\";");
                sb.AppendLine($"    interval {script.Interval};");
                sb.AppendLine($"    weight {script.Weight};");

                if (script.Fall > 0)
                    sb.AppendLine($"    fall {script.Fall};");

                if (script.Rise > 0)
                    sb.AppendLine($"    rise {script.Rise};");

                if (script.Timeout > 0)
                    sb.AppendLine($"    timeout {script.Timeout};");

                sb.AppendLine("}");
            }

            // 生成vrrp_instances
            foreach (var instance in config.VrrpInstances)
            {
                sb.AppendLine();
                sb.AppendLine($"vrrp_instance {instance.Name} {{");
                sb.AppendLine($"    state {instance.State};");
                sb.AppendLine($"    interface {instance.Interface};");
                sb.AppendLine($"    virtual_router_id {instance.VirtualRouterId};");
                sb.AppendLine($"    priority {instance.Priority};");
                sb.AppendLine($"    advert_int {instance.AdvertInt};");

                sb.AppendLine("    authentication {");
                sb.AppendLine($"        auth_type {instance.Authentication.AuthType};");
                sb.AppendLine($"        auth_pass {instance.Authentication.AuthPass};");
                sb.AppendLine("    }");

                sb.AppendLine("    virtual_ipaddress {");
                foreach (var vip in instance.VirtualIpAddresses)
                    sb.AppendLine($"        {vip.IpAddress};");
                sb.AppendLine("    }");

                if (instance.Nopreempt)
                    sb.AppendLine("    nopreempt;");

                if (instance.PreemptDelay > 0)
                    sb.AppendLine($"    preempt_delay {instance.PreemptDelay};");

                if (instance.TrackScripts.Any())
                {
                    sb.AppendLine("    track_script {");
                    foreach (var script in instance.TrackScripts)
                        sb.AppendLine($"        {script.ScriptName};");
                    sb.AppendLine("    }");
                }

                if (!string.IsNullOrEmpty(instance.NotificationScripts.Master) ||
                    !string.IsNullOrEmpty(instance.NotificationScripts.Backup) ||
                    !string.IsNullOrEmpty(instance.NotificationScripts.Fault))
                {
                    sb.AppendLine("    notify \"/etc/keepalived/notify.sh\";");
                }

                sb.AppendLine("}");
            }

            // 生成virtual_servers
            foreach (var vs in config.VirtualServers)
            {
                sb.AppendLine();
                sb.AppendLine($"virtual_server {vs.IpAddress} {vs.Port} {{");
                sb.AppendLine($"    delay_loop {vs.DelayLoop};");
                sb.AppendLine($"    lb_algo {vs.LbAlgo};");
                sb.AppendLine($"    lb_kind {vs.LbKind};");

                if (vs.Persistence)
                    sb.AppendLine($"    persistence_timeout {vs.PersistenceTimeout};");

                sb.AppendLine($"    protocol {vs.Protocol};");

                if (vs.Timeout != null)
                {
                    sb.AppendLine("    persistence_granularity <NETMASK>;");
                    sb.AppendLine($"    timeout client {vs.Timeout.Client};");
                    sb.AppendLine($"    timeout connect {vs.Timeout.Connect};");
                    sb.AppendLine($"    timeout server {vs.Timeout.Server};");
                }

                foreach (var rs in vs.RealServers.Where(r => r.Enabled))
                {
                    sb.AppendLine();
                    sb.AppendLine($"    real_server {rs.IpAddress} {rs.Port} {{");
                    sb.AppendLine($"        weight {rs.Weight};");

                    if (rs.HealthCheck != null)
                    {
                        if (rs.HealthCheck is TcpHealthCheck tcp)
                        {
                            sb.AppendLine("        TCP_CHECK {");
                            sb.AppendLine($"            connect_port {tcp.ConnectPort};");
                            sb.AppendLine($"            connect_timeout {tcp.ConnectTimeout};");
                            sb.AppendLine($"            nb_get_retry {tcp.RetryCount};");
                            sb.AppendLine($"            delay_before_retry {tcp.DelayBeforeRetry};");
                            sb.AppendLine("        }");
                        }
                        else if (rs.HealthCheck is HttpHealthCheck http)
                        {
                            sb.AppendLine("        HTTP_GET {");
                            sb.AppendLine("            url {");
                            sb.AppendLine($"                path {http.UrlPath};");
                            sb.AppendLine($"                status_code {http.StatusCode};");
                            sb.AppendLine("            }");
                            sb.AppendLine($"            connect_timeout {http.ConnectTimeout};");
                            sb.AppendLine($"            nb_get_retry {http.RetryCount};");
                            sb.AppendLine($"            delay_before_retry {http.DelayBeforeRetry};");
                            sb.AppendLine("        }");
                        }
                    }

                    sb.AppendLine("    }");
                }

                sb.AppendLine("}");
            }

            return sb.ToString();
        }

        #region 解析节点

        private void ParseGlobalDefs(GlobalDefs defs, string[] lines, ref int index)
        {
            while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
            {
                var line = lines[index].Trim();
                if (line.StartsWith("router_id"))
                    defs.RouterId = line.Split(' ')[1].TrimEnd(';');
                else if (line.StartsWith("notification_email"))
                    ParseNotificationEmails(defs, lines, ref index);
                else if (line.StartsWith("notification_email_from"))
                    defs.NotificationEmailFrom = line.Split(' ')[1].TrimEnd(';');
                else if (line.StartsWith("smtp_server"))
                    defs.SmtpServer = line.Split(' ')[1].TrimEnd(';');
                else if (line.StartsWith("smtp_connect_timeout"))
                    defs.SmtpConnectTimeout = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line == "vrrp_strict")
                    defs.VrrpStrict = true;
                else if (line == "vrrp_skip_check_adv_addr")
                    defs.VrrpSkipCheckAdvAddr = true;
                else if (line.StartsWith("vrrp_garp_interval"))
                    defs.VrrpGarpInterval = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("vrrp_gna_interval"))
                    defs.VrrpGnaInterval = int.Parse(line.Split(' ')[1].TrimEnd(';'));
            }
        }

        private void ParseNotificationEmails(GlobalDefs defs, string[] lines, ref int index)
        {
            var line = lines[index].Trim();
            if (line.EndsWith("{"))
            {
                while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
                {
                    var emailLine = lines[index].Trim();
                    if (!string.IsNullOrWhiteSpace(emailLine))
                        defs.NotificationEmails.Add(emailLine);
                }
            }
            else
            {
                // 单行格式: notification_email email1 email2
                var emails = line.Split(' ').Skip(1).Select(e => e.TrimEnd(';'));
                defs.NotificationEmails.AddRange(emails);
            }
        }

        private VrrpInstance ParseVrrpInstance(string[] lines, ref int index)
        {
            var instance = new VrrpInstance();
            var firstLine = lines[index].Trim();
            instance.Name = firstLine.Split(' ')[1];

            while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
            {
                var line = lines[index].Trim();
                if (line.StartsWith("state"))
                    instance.State = line.Split(' ')[1].TrimEnd(';');
                else if (line.StartsWith("interface"))
                    instance.Interface = line.Split(' ')[1].TrimEnd(';');
                else if (line.StartsWith("virtual_router_id"))
                    instance.VirtualRouterId = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("priority"))
                    instance.Priority = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("advert_int"))
                    instance.AdvertInt = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("authentication"))
                    ParseVrrpAuthentication(instance.Authentication, lines, ref index);
                else if (line.StartsWith("virtual_ipaddress"))
                    ParseVirtualIpAddresses(instance.VirtualIpAddresses, lines, ref index);
                else if (line == "nopreempt")
                    instance.Nopreempt = true;
                else if (line.StartsWith("preempt_delay"))
                    instance.PreemptDelay = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("track_script"))
                    ParseTrackScripts(instance.TrackScripts, lines, ref index);
                else if (line.StartsWith("notify_master") || line.StartsWith("notify_backup") || line.StartsWith("notify_fault"))
                    ParseNotificationScripts(instance.NotificationScripts, line);
            }

            return instance;
        }

        private void ParseVrrpAuthentication(VrrpAuthentication auth, string[] lines, ref int index)
        {
            while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
            {
                var line = lines[index].Trim();
                if (line.StartsWith("auth_type"))
                    auth.AuthType = line.Split(' ')[1].TrimEnd(';');
                else if (line.StartsWith("auth_pass"))
                    auth.AuthPass = line.Split(' ')[1].TrimEnd(';');
            }
        }

        private void ParseVirtualIpAddresses(List<VirtualIpAddress> vips, string[] lines, ref int index)
        {
            while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
            {
                var line = lines[index].Trim();
                if (!string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"))
                    vips.Add(new VirtualIpAddress() { IpAddress= line.TrimEnd(';') });
            }
        }

        private void ParseTrackScripts(List<VrrpTrackScript> scripts, string[] lines, ref int index)
        {
            while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
            {
                var line = lines[index].Trim();
                if (!string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"))
                    scripts.Add(new VrrpTrackScript { ScriptName = line.TrimEnd(';') });
            }
        }

        private void ParseNotificationScripts(NotificationScripts scripts, string line)
        {
            var parts = line.Split(new[] { ' ' }, 2);
            var scriptPath = parts[1].Trim().Trim('"').TrimEnd(';');

            if (line.StartsWith("notify_master"))
                scripts.Master = scriptPath;
            else if (line.StartsWith("notify_backup"))
                scripts.Backup = scriptPath;
            else if (line.StartsWith("notify_fault"))
                scripts.Fault = scriptPath;
            else if (line.StartsWith("notify"))
                scripts.Any = scriptPath;
        }

        private VrrpScript ParseVrrpScript(string[] lines, ref int index)
        {
            var script = new VrrpScript();
            var firstLine = lines[index].Trim();
            script.Name = firstLine.Split(' ')[1];

            while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
            {
                var line = lines[index].Trim();
                if (line.StartsWith("script"))
                    script.Script = line.Split(new[] { ' ' }, 2)[1].Trim().Trim('"').TrimEnd(';');
                else if (line.StartsWith("interval"))
                    script.Interval = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("weight"))
                    script.Weight = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("fall"))
                    script.Fall = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("rise"))
                    script.Rise = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("timeout"))
                    script.Timeout = int.Parse(line.Split(' ')[1].TrimEnd(';'));
            }

            return script;
        }

        private VirtualServer ParseVirtualServer(string[] lines, ref int index)
        {
            var server = new VirtualServer();
            var firstLine = lines[index].Trim().Split(' ');
            server.IpAddress = firstLine[1];
            server.Port = int.Parse(firstLine[2]);

            while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
            {
                var line = lines[index].Trim();
                if (line.StartsWith("delay_loop"))
                    server.DelayLoop = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("lb_algo"))
                    server.LbAlgo = line.Split(' ')[1].TrimEnd(';');
                else if (line.StartsWith("lb_kind"))
                    server.LbKind = line.Split(' ')[1].TrimEnd(';');
                else if (line.StartsWith("persistence_timeout"))
                {
                    server.Persistence = true;
                    server.PersistenceTimeout = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                }
                else if (line.StartsWith("protocol"))
                    server.Protocol = line.Split(' ')[1].TrimEnd(';');
                else if (line.StartsWith("real_server"))
                    server.RealServers.Add(ParseRealServer(lines, ref index));
                else if (line.StartsWith("timeout"))
                    ParseTimeoutSettings(server.Timeout, line);
            }

            return server;
        }

        private RealServer ParseRealServer(string[] lines, ref int index)
        {
            var server = new RealServer();
            var firstLine = lines[index].Trim().Split(' ');
            server.IpAddress = firstLine[1];
            server.Port = int.Parse(firstLine[2]);

            while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
            {
                var line = lines[index].Trim();
                if (line.StartsWith("weight"))
                    server.Weight = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("TCP_CHECK"))
                    server.HealthCheck = ParseTcpHealthCheck(lines, ref index);
                else if (line.StartsWith("HTTP_GET"))
                    server.HealthCheck = ParseHttpHealthCheck(lines, ref index);
                else if (line.StartsWith("SSL_GET"))
                    server.HealthCheck = ParseSslHealthCheck(lines, ref index);
            }

            return server;
        }

        private TcpHealthCheck ParseTcpHealthCheck(string[] lines, ref int index)
        {
            var check = new TcpHealthCheck();

            while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
            {
                var line = lines[index].Trim();
                if (line.StartsWith("connect_port"))
                    check.ConnectPort = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("connect_timeout"))
                    check.ConnectTimeout = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("nb_get_retry"))
                    check.RetryCount = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("delay_before_retry"))
                    check.DelayBeforeRetry = int.Parse(line.Split(' ')[1].TrimEnd(';'));
            }

            return check;
        }

        private HttpHealthCheck ParseHttpHealthCheck(string[] lines, ref int index)
        {
            var check = new HttpHealthCheck();

            while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
            {
                var line = lines[index].Trim();
                if (line.StartsWith("url"))
                {
                    while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
                    {
                        var urlLine = lines[index].Trim();
                        if (urlLine.StartsWith("path"))
                            check.UrlPath = urlLine.Split(' ')[1].TrimEnd(';');
                        else if (urlLine.StartsWith("status_code"))
                            check.StatusCode = int.Parse(urlLine.Split(' ')[1].TrimEnd(';'));
                        else if (urlLine.StartsWith("digest"))
                            check.ExpectResponse = urlLine.Split(' ')[1].TrimEnd(';');
                    }
                }
                else if (line.StartsWith("connect_timeout"))
                    check.ConnectTimeout = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("nb_get_retry"))
                    check.RetryCount = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("delay_before_retry"))
                    check.DelayBeforeRetry = int.Parse(line.Split(' ')[1].TrimEnd(';'));
            }

            return check;
        }

        private SslHealthCheck ParseSslHealthCheck(string[] lines, ref int index)
        {
            var check = new SslHealthCheck();

            while (++index < lines.Length && !lines[index].Trim().StartsWith("}"))
            {
                var line = lines[index].Trim();
                if (line.StartsWith("connect_port"))
                    check.ConnectPort = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("connect_timeout"))
                    check.ConnectTimeout = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("nb_get_retry"))
                    check.RetryCount = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("delay_before_retry"))
                    check.DelayBeforeRetry = int.Parse(line.Split(' ')[1].TrimEnd(';'));
                else if (line.StartsWith("expect cn"))
                    check.ExpectCn = line.Split(new[] { ' ' }, 3)[2].TrimEnd(';');
            }

            return check;
        }

        private void ParseTimeoutSettings(TimeoutSettings timeout, string line)
        {
            var parts = line.Split(' ');
            var value = int.Parse(parts[2].TrimEnd(';'));

            if (parts[1] == "client")
                timeout.Client = value;
            else if (parts[1] == "connect")
                timeout.Connect = value;
            else if (parts[1] == "server")
                timeout.Server = value;
        }

        #endregion
    }
}
